PropertyWrappedCodable
Nice syntax for defaults and custom keys with Codable using Property Wrappers and Mirror.
Note: About 5x slower than normal Codable
// initialising 1000x takes ~ 0.046s
struct WrappedExample: PropertyWrappedCodable {
@CodableValue var name: String
@CodableValue var id: String = "Default"
@CodableValue var dog: String?
@CodableValue(path: "is_active") var isActive: Bool
@CodableValue(path: "nested", "value") var nestedValue: String
init(nonWrappedPropertiesFrom decoder: Decoder) throws {}
}
vs
// initialising 1000x takes ~ 0.008s
struct CodableExample: Codable {
var name: String
var id: String
var dog: String
var isActive: Bool
var nested: Nested
struct Nested: Codable {
var value: String
}
enum CodingKeys: String, CodingKey {
case name
case id
case dog
case isActive = "is_active"
case nested
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
id = (try? container.decode(String.self, forKey: .id)) ?? "Default"
dog = try container.decode(String.self, forKey: .dog)
isActive = try container.decode(Bool.self, forKey: .isActive)
nested = try container.decode(Nested.self, forKey: .nested)
}
}
CodableID
@CodableID
refers to the key of the object that is being decoded.
struct Example: PropertyWrappedCodable {
@CodableID var id: String
@CodableValue var name: String
@CodableValue var isActive: Bool
init(nonWrappedPropertiesFrom decoder: Decoder) throws {}
}
let json = """
{
"example-id": { "name": "Amzd", "isActive": true }
}
"""
let data = json.data(using: .utf8)!
let example = try decoder.decode([String: Example].self, from: data)
print(example.values.first?.id) // "example-id"
FamilyCodable
Let the data decide the type
class Pet: FamilyCodable {
@CodableValue() var name: String
@CodableValue() private var type: String
required init(nonWrappedPropertiesFrom decoder: Decoder) throws { }
static var discriminatorKey = "type"
final class func familyMember(for value: String) throws -> Codable.Type {
switch value {
case "Cat": return Cat.self
case "Dog": return Dog.self
default: return Pet.self
}
}
}
class Cat: Pet {
@CodableValue() var lives: Int
}
class Dog: Pet {
func fetch() { }
}
let petsJson = """
[{ "type": "Cat", "name": "Garfield", "lives": 9 },
{ "type": "Dog", "name": "Pluto" }]
"""
let petsData = petsJson.data(using: .utf8)!
let pets = try decoder.decode([Pet].self, from: petsData) // [Cat, Dog]
Collection Decoding Strategy
public enum CollectionDecodingStrategy<V> {
/// Replaces invalid elements with fallback value:
/// ["This", null, "That"] -> ["This", "FallbackValue", "That"]
/// This is the default with `nil` as fallback value if the collection uses an Optional type (eg: [Int?])
case fallbackValue(V)
/// Leaves out invalid elements:
/// [1, 2, "3"] -> [1, 2]
/// This is the default unless the collection uses an Optional type (eg: [Int?])
/// Note: Throws when there is no collection! Use default if you don't want that.
case lossy
}
Usage:
struct Example: PropertyWrappedCodable {
// defaults to .lossy so failed decoding wont be shown
@CodableCollection() var ids1: [Int]
// same as ids1
@CodableCollection(.lossy) var ids2: [Int]
// defaults fallback to `nil`
@CodableCollection() var ids3: [Int?]
// same as ids3
@CodableCollection(.fallbackValue(nil)) var ids4: [Int?]
// falls back to 0 if decoding fails
@CodableCollection(.fallbackValue(0)) var ids5: [Int]
init(nonWrappedPropertiesFrom decoder: Decoder) throws { }
// Optional:
// If you want to report back that some objects are the wrong structure and couldn't be decoded you can do that like this:
init(from decoder: Decoder) throws {
try self.init(wrappedPropertiesFrom: decoder)
_ids1.failures.isEmpty ? () : Admin.sendReport("Failed to init some objects: \(_ids1.failures)")
}
// Optional:
// If you want to expose the errors you can do this:
var ids1Failures: [Error] {
_ids1.failures
}
}
let json = """
{
"ids1" : [1, 2, "3"],
"ids2" : [1, 2, "3"],
"ids3" : [1, 2, "3"],
"ids4" : [1, 2, "3"],
"ids5" : [1, 2, "3"]
}
"""
let data = json.data(using: .utf8)!
let example = try decoder.decode(Example.self, from: data)
print(example.ids1) // [1, 2]
print(example.ids2) // [1, 2]
print(example.ids3) // [1, 2, nil]
print(example.ids4) // [1, 2, nil]
print(example.ids5) // [1, 2, 0]